package minijvm.cmd;

import minijvm.clz.ClassFile;
import minijvm.constant.MethodRefInfo;
import minijvm.engine.ExecutionResult;
import minijvm.engine.JavaObject;
import minijvm.engine.MethodArea;
import minijvm.engine.StackFrame;
import minijvm.method.Method;

/**
 * @author ChangLiang
 * @date 2020/4/26
 */
public class InvokeVirtualCmd extends TwoOperandCmd {
    public InvokeVirtualCmd(ClassFile clzFile, String opCode) {
        super(clzFile, opCode);
    }

    @Override
    public void execute(StackFrame frame, ExecutionResult result) {
        System.out.println("invoke virtual cmd execute");
        // 从常量池中找到方法的描述
        MethodRefInfo methodRefInfo = (MethodRefInfo) this.getConstantInfo(this.getIndex());
        // 在方法的描述中找到类名和方法名
        String className = methodRefInfo.getClassName();
        String methodName = methodRefInfo.getMethodName();

        // 因为我们没有实现java中System类的out.print方法，所以这里就直接短路处理，不形成新的栈帧
        if(isSystemOutPrintlnMethod(className,methodName)){
            JavaObject javaObject = frame.getOperandStack().pop();
            String value = javaObject.toString();
            System.err.print("----------------------");
            System.err.print(value);
            System.err.println("----------------------");
            return;
        }
        // 多态实现
        JavaObject javaObject = frame.getOperandStack().peek();
        MethodArea methodArea = MethodArea.getInstance();
        Method method = null;
        String currentClassName = javaObject.getClassName();
        while (currentClassName != null) {
            ClassFile currentClassFile = methodArea.findClassFile(currentClassName);
            method = currentClassFile.getMethod(methodRefInfo.getMethodName(), methodRefInfo.getParamAndReturnType());
            if (method != null) {
                break;
            } else {
                currentClassName = currentClassFile.getSuperClassName();
            }
        }
        if (method == null) {
            throw new RuntimeException("Can't find method for: "+methodRefInfo);
        }
        result.setNextAction(ExecutionResult.PAUSE_AND_RUN_NEW_FRAME);
        result.setNextMethod(method);
    }

    private boolean isSystemOutPrintlnMethod(String className, String methodName) {
        return "java/io/PrintStream".equals(className)
                && "println".equals(methodName);
    }
}
